home *** CD-ROM | disk | FTP | other *** search
/ Apple Developer Connection Student Program / ADC Tools Sampler CD Disk 3 1999.iso / Metrowerks CodeWarrior / Java Support / Java_Source / Java2 / src / java / util / WeakHashMap.java < prev   
Encoding:
Java Source  |  1999-05-28  |  13.7 KB  |  438 lines  |  [TEXT/CWIE]

  1. /*
  2.  * @(#)WeakHashMap.java    1.5 98/09/30
  3.  * 
  4.  * Copyright 1998 by Sun Microsystems, Inc.,
  5.  * 901 San Antonio Road, Palo Alto, California, 94303, U.S.A.
  6.  * All rights reserved.
  7.  * 
  8.  * This software is the confidential and proprietary information
  9.  * of Sun Microsystems, Inc. ("Confidential Information").  You
  10.  * shall not disclose such Confidential Information and shall use
  11.  * it only in accordance with the terms of the license agreement
  12.  * you entered into with Sun.
  13.  */
  14.  
  15. package java.util;
  16.  
  17. import java.util.Iterator;
  18. import java.util.Map;
  19. import java.util.AbstractMap;
  20. import java.util.HashMap;
  21. import java.util.Set;
  22. import java.util.AbstractSet;
  23. import java.util.NoSuchElementException;
  24.  
  25. import java.lang.ref.WeakReference;
  26. import java.lang.ref.ReferenceQueue;
  27.  
  28.  
  29. /**
  30.  * A hashtable-based <code>Map</code> implementation with <em>weak keys</em>.
  31.  * An entry in a <code>WeakHashMap</code> will automatically be removed when
  32.  * its key is no longer in ordinary use.  More precisely, the presence of a
  33.  * mapping for a given key will not prevent the key from being discarded by the
  34.  * garbage collector, that is, made finalizable, finalized, and then reclaimed.
  35.  * When a key has been discarded its entry is effectively removed from the map,
  36.  * so this class behaves somewhat differently than other <code>Map</code>
  37.  * implementations.
  38.  *
  39.  * <p> Both null values and the null key are supported.  This class has
  40.  * performance characteristics similar to those of the <code>HashMap</code>
  41.  * class, and has the same efficiency parameters of <em>initial capacity</em>
  42.  * and <em>load factor</em>.
  43.  *
  44.  * <p> Like most collection classes, this class is not synchronized.  A
  45.  * synchronized <code>WeakHashMap</code> may be constructed using the
  46.  * <code>Collections.synchronizedMap</code> method.
  47.  *
  48.  * <p> This class is intended primarily for use with key objects whose
  49.  * <code>equals</code> methods test for object identity using the
  50.  * <code>==</code> operator.  Once such a key is discarded it can never be
  51.  * recreated, so it is impossible to do a lookup of that key in a
  52.  * <code>WeakHashMap</code> at some later time and be surprised that its entry
  53.  * has been removed.  This class will work perfectly well with key objects
  54.  * whose <code>equals</code> methods are not based upon object identity, such
  55.  * as <code>String</code> instances.  With such recreatable key objects,
  56.  * however, the automatic removal of <code>WeakHashMap</code> entries whose
  57.  * keys have been discarded may prove to be confusing.
  58.  *
  59.  * <p> The behavior of the <code>WeakHashMap</code> class depends in part upon
  60.  * the actions of the garbage collector, so several familiar (though not
  61.  * required) <code>Map</code> invariants do not hold for this class.  Because
  62.  * the garbage collector may discard keys at any time, a
  63.  * <code>WeakHashMap</code> may behave as though an unknown thread is silently
  64.  * removing entries.  In particular, even if you synchronize on a
  65.  * <code>WeakHashMap</code> instance and invoke none of its mutator methods, it
  66.  * is possible for the <code>size</code> method to return smaller values over
  67.  * time, for the <code>isEmpty</code> method to return <code>false</code> and
  68.  * then <code>true</code>, for the <code>containsKey</code> method to return
  69.  * <code>true</code> and later <code>false</code> for a given key, for the
  70.  * <code>get</code> method to return a value for a given key but later return
  71.  * <code>null</code>, for the <code>put</code> method to return
  72.  * <code>null</code> and the <code>remove</code> method to return
  73.  * <code>false</code> for a key that previously appeared to be in the map, and
  74.  * for successive examinations of the key set, the value set, and the entry set
  75.  * to yield successively smaller numbers of elements.
  76.  *
  77.  * <p> Each key object in a <code>WeakHashMap</code> is stored indirectly as
  78.  * the referent of a weak reference.  Therefore a key will automatically be
  79.  * removed only after the weak references to it, both inside and outside of the
  80.  * map, have been cleared by the garbage collector.
  81.  *
  82.  * <p> <strong>Implementation note:</strong> The value objects in a
  83.  * <code>WeakHashMap</code> are held by ordinary strong references.  Thus care
  84.  * should be taken to ensure that value objects do not strongly refer to their
  85.  * own keys, either directly or indirectly, since that will prevent the keys
  86.  * from being discarded.  Note that a value object may refer indirectly to its
  87.  * key via the <code>WeakHashMap</code> itself; that is, a value object may
  88.  * strongly refer to some other key object whose associated value object, in
  89.  * turn, strongly refers to the key of the first value object.  This problem
  90.  * may be fixed in a future release.
  91.  *
  92.  * @version    1.5, 98/09/30
  93.  * @author    Mark Reinhold
  94.  * @since    JDK1.2
  95.  * @see        java.util.HashMap
  96.  * @see        java.lang.ref.WeakReference
  97.  */
  98.  
  99. public class WeakHashMap extends AbstractMap implements Map {
  100.  
  101.     /* A WeakHashMap is implemented as a HashMap that maps WeakKeys to values.
  102.        Because we don't have access to the innards of the HashMap, the various
  103.        query methods must create a temporary WeakKey every time a lookup is
  104.        done.  Fortunately these are small, short-lived objects, so the added
  105.        allocation overhead is tolerable. */
  106.  
  107.  
  108.     static private class WeakKey extends WeakReference {
  109.     private int hash;    /* Hashcode of key, stored here since the key
  110.                    may be tossed by the GC */
  111.  
  112.     private WeakKey(Object k) {
  113.         super(k);
  114.         hash = k.hashCode();
  115.     }
  116.  
  117.     private static WeakKey create(Object k) {
  118.         if (k == null) return null;
  119.         else return new WeakKey(k);
  120.     }
  121.  
  122.     private WeakKey(Object k, ReferenceQueue q) {
  123.         super(k, q);
  124.         hash = k.hashCode();
  125.     }
  126.  
  127.     private static WeakKey create(Object k, ReferenceQueue q) {
  128.         if (k == null) return null;
  129.         else return new WeakKey(k, q);
  130.     }
  131.  
  132.         /* A WeakKey is equal to another WeakKey iff they both refer to objects
  133.        that are, in turn, equal according to their own equals methods */
  134.     public boolean equals(Object o) {
  135.         if (this == o) return true;
  136.         if (!(o instanceof WeakKey)) return false;
  137.         Object t = this.get();
  138.         Object u = ((WeakKey)o).get();
  139.         if ((t == null) || (u == null)) return false;
  140.         if (t == u) return true;
  141.         return t.equals(u);
  142.     }
  143.  
  144.     public int hashCode() {
  145.         return hash;
  146.     }
  147.  
  148.     }
  149.  
  150.  
  151.     /* Hash table mapping WeakKeys to values */
  152.     private Map hash;
  153.  
  154.     /* Reference queue for cleared WeakKeys */
  155.     private ReferenceQueue queue = new ReferenceQueue();
  156.  
  157.  
  158.     /* Remove all invalidated entries from the map, that is, remove all entries
  159.        whose keys have been discarded.  This method should be invoked once by
  160.        each public mutator in this class.  We don't invoke this method in
  161.        public accessors because that can lead to surprising
  162.        ConcurrentModificationExceptions. */
  163.     private void processQueue() {
  164.     WeakKey wk;
  165.     while ((wk = (WeakKey)queue.poll()) != null) {
  166.         hash.remove(wk);
  167.     }
  168.     }
  169.  
  170.  
  171.     /* -- Constructors -- */
  172.  
  173.     /**
  174.      * Constructs a new, empty <code>WeakHashMap</code> with the given
  175.      * initial capacity and the given load factor.
  176.      *
  177.      * @param  initialCapacity  The initial capacity of the
  178.      *                          <code>WeakHashMap</code>
  179.      *
  180.      * @param  loadFactor       The load factor of the <code>WeakHashMap</code>
  181.      *
  182.      * @throws IllegalArgumentException  If the initial capacity is less than
  183.      *                                   zero, or if the load factor is
  184.      *                                   nonpositive
  185.      */
  186.     public WeakHashMap(int initialCapacity, float loadFactor) {
  187.     hash = new HashMap(initialCapacity, loadFactor);
  188.     }
  189.  
  190.     /**
  191.      * Constructs a new, empty <code>WeakHashMap</code> with the given
  192.      * initial capacity and the default load factor, which is
  193.      * <code>0.75</code>.
  194.      *
  195.      * @param  initialCapacity  The initial capacity of the
  196.      *                          <code>WeakHashMap</code>
  197.      *
  198.      * @throws IllegalArgumentException  If the initial capacity is less than
  199.      *                                   zero
  200.      */
  201.     public WeakHashMap(int initialCapacity) {
  202.     hash = new HashMap(initialCapacity);
  203.     }
  204.  
  205.     /**
  206.      * Constructs a new, empty <code>WeakHashMap</code> with the default
  207.      * capacity and the default load factor, which is <code>0.75</code>.
  208.      */
  209.     public WeakHashMap() {
  210.     hash = new HashMap();
  211.     }
  212.  
  213.  
  214.     /* -- Simple queries -- */
  215.  
  216.     /**
  217.      * Returns the number of key-value mappings in this map.
  218.      * <strong>Note:</strong> <em>In contrast with most implementations of the
  219.      * <code>Map</code> interface, the time required by this operation is
  220.      * linear in the size of the map.</em>
  221.      */
  222.     public int size() {
  223.     return entrySet().size();
  224.     }
  225.  
  226.     /**
  227.      * Returns <code>true</code> if this map contains no key-value mappings.
  228.      */
  229.     public boolean isEmpty() {
  230.     return entrySet().isEmpty();
  231.     }
  232.  
  233.     /**
  234.      * Returns <code>true</code> if this map contains a mapping for the
  235.      * specified key.
  236.      *
  237.      * @param   key   The key whose presence in this map is to be tested
  238.      */
  239.     public boolean containsKey(Object key) {
  240.     return hash.containsKey(WeakKey.create(key));
  241.     }
  242.  
  243.  
  244.     /* -- Lookup and modification operations -- */
  245.  
  246.     /**
  247.      * Returns the value to which this map maps the specified <code>key</code>.
  248.      * If this map does not contain a value for this key, then return
  249.      * <code>null</code>.
  250.      *
  251.      * @param  key  The key whose associated value, if any, is to be returned
  252.      */
  253.     public Object get(Object key) {
  254.     return hash.get(WeakKey.create(key));
  255.     }
  256.  
  257.     /**
  258.      * Updates this map so that the given <code>key</code> maps to the given
  259.      * <code>value</code>.  If the map previously contained a mapping for
  260.      * <code>key</code> then that mapping is replaced and the previous value is
  261.      * returned.
  262.      *
  263.      * @param  key    The key that is to be mapped to the given
  264.      *                <code>value</code> 
  265.      * @param  value  The value to which the given <code>key</code> is to be
  266.      *                mapped
  267.      *
  268.      * @return  The previous value to which this key was mapped, or
  269.      *          <code>null</code> if if there was no mapping for the key
  270.      */
  271.     public Object put(Object key, Object value) {
  272.     processQueue();
  273.     return hash.put(WeakKey.create(key, queue), value);
  274.     }
  275.  
  276.     /**
  277.      * Removes the mapping for the given <code>key</code> from this map, if
  278.      * present.
  279.      *
  280.      * @param  key  The key whose mapping is to be removed
  281.      *
  282.      * @return  The value to which this key was mapped, or <code>null</code> if
  283.      *          there was no mapping for the key
  284.      */
  285.     public Object remove(Object key) {
  286.     processQueue();
  287.     return hash.remove(WeakKey.create(key));
  288.     }
  289.  
  290.     /**
  291.      * Removes all mappings from this map.
  292.      */
  293.     public void clear() {
  294.     processQueue();
  295.     hash.clear();
  296.     }
  297.  
  298.  
  299.     /* -- Views -- */
  300.  
  301.  
  302.     /* Internal class for entries */
  303.     static private class Entry implements Map.Entry {
  304.     private Map.Entry ent;
  305.     private Object key;    /* Strong reference to key, so that the GC
  306.                    will leave it alone as long as this Entry
  307.                    exists */
  308.  
  309.     Entry(Map.Entry ent, Object key) {
  310.         this.ent = ent;
  311.         this.key = key;
  312.     }
  313.  
  314.     public Object getKey() {
  315.         return key;
  316.     }
  317.  
  318.     public Object getValue() {
  319.         return ent.getValue();
  320.     }
  321.  
  322.     public Object setValue(Object value) {
  323.         return ent.setValue(value);
  324.     }
  325.  
  326.     private static boolean valEquals(Object o1, Object o2) {
  327.         return (o1 == null) ? (o2 == null) : o1.equals(o2);
  328.     }
  329.  
  330.     public boolean equals(Object o) {
  331.         if (! (o instanceof Map.Entry)) return false;
  332.         Map.Entry e = (Map.Entry)o;
  333.         return (valEquals(key, e.getKey())
  334.             && valEquals(getValue(), e.getValue()));
  335.     }
  336.  
  337.     public int hashCode() {
  338.         Object v;
  339.         return (((key == null) ? 0 : key.hashCode())
  340.             ^ (((v = getValue()) == null) ? 0 : v.hashCode()));
  341.     }
  342.  
  343.     }
  344.  
  345.  
  346.     /* Internal class for entry sets */
  347.     private class EntrySet extends AbstractSet {
  348.     Set hashEntrySet = hash.entrySet();
  349.  
  350.     public Iterator iterator() {
  351.  
  352.         return new Iterator() {
  353.         Iterator hashIterator = hashEntrySet.iterator();
  354.         Entry next = null;
  355.  
  356.         public boolean hasNext() {
  357.             while (hashIterator.hasNext()) {
  358.             Map.Entry ent = (Map.Entry)hashIterator.next();
  359.             WeakKey wk = (WeakKey)ent.getKey();
  360.             Object k = null;
  361.             if ((wk != null) && ((k = wk.get()) == null)) {
  362.                 /* Weak key has been cleared by GC */
  363.                 continue;
  364.             }
  365.             next = new Entry(ent, k);
  366.             return true;
  367.             }
  368.             return false;
  369.         }
  370.  
  371.         public Object next() {
  372.             if ((next == null) && !hasNext())
  373.             throw new NoSuchElementException();
  374.             Entry e = next;
  375.             next = null;
  376.             return e;
  377.         }
  378.  
  379.         public void remove() {
  380.             hashIterator.remove();
  381.         }
  382.  
  383.         };
  384.     }
  385.  
  386.     public boolean isEmpty() {
  387.         return !(iterator().hasNext());
  388.     }
  389.  
  390.     public int size() {
  391.         int j = 0;
  392.         for (Iterator i = iterator(); i.hasNext(); i.next()) j++;
  393.         return j;
  394.     }
  395.  
  396.     public boolean remove(Object o) {
  397.         processQueue();
  398.         if (!(o instanceof Map.Entry)) return false;
  399.         Map.Entry e = (Map.Entry)o;
  400.         Object ev = e.getValue();
  401.         WeakKey wk = WeakKey.create(e.getKey());
  402.         Object hv = hash.get(wk);
  403.         if ((hv == null)
  404.         ? ((ev == null) && hash.containsKey(wk)) : hv.equals(ev)) {
  405.         hash.remove(wk);
  406.         return true;
  407.         }
  408.         return false;
  409.     }
  410.  
  411.     public int hashCode() {
  412.         int h = 0;
  413.         for (Iterator i = hashEntrySet.iterator(); i.hasNext();) {
  414.         Map.Entry ent = (Map.Entry)i.next();
  415.         WeakKey wk = (WeakKey)ent.getKey();
  416.         Object v;
  417.         if (wk == null) continue;
  418.         h += (wk.hashCode()
  419.               ^ (((v = ent.getValue()) == null) ? 0 : v.hashCode()));
  420.         }
  421.         return h;
  422.     }
  423.  
  424.     }
  425.  
  426.  
  427.     private Set entrySet = null;
  428.  
  429.     /**
  430.      * Returns a <code>Set</code> view of the mappings in this map.
  431.      */
  432.     public Set entrySet() {
  433.     if (entrySet == null) entrySet = new EntrySet();
  434.     return entrySet;
  435.     }
  436.  
  437. }
  438.